# frozen_string_literal: true

require_relative('vm-move-path')

class KlotskiViewModel
  def initialize(gui, initial_board = Game.instance.initial_board)
    super()
    @task_agent = gui.task_agent

    @solve_enabled = bind_button(gui.observable(:solve))  {on_solve}[:enabled]
    @undo_enabled  = bind_button(gui.observable(:undo))   {@move_path.recede}[:enabled]
    @next_enabled  = bind_button(gui.observable(:next))   {@move_path.advance}[:enabled]
    @reset_enabled = bind_button(gui.observable(:reset))  {on_reset}[:enabled]

    @status_text        = bind_label(gui.observable(:status))[:text]
    @solve_details_text = bind_label(gui.observable(:solve_details))[:text]

    @board,
    @board_next_move = bind_board(
      gui.observable(:board)
    ) {|c, dir| on_move(c, dir)}.values_at(
      :board,
      :next_move
    )

    gui.observable(:game_select) do |o|
      o.on_event(:new_game) {|game| on_new_game(game)}
    end

    Solver.instance.observable do |o|
      o.on_event(:trying_candidates) {|paths, depth| on_solver_trying_candidates(paths, depth)}
      o.on_event(:wins_foud)         {|paths, depth| on_solver_wins_found(paths, depth)}
      o.on_event(:solutions_found)   {|solutions|    on_solutions_found(solutions)}
    end

    (@move_path = MovePath.new).observable do |o|
      @current_board = AttributeVMBinding.new(o, :current_board)
      o.on_event(:can_advance) {|value| @next_enabled.value    = value}
      o.on_event(:can_recede)  {|value| @undo_enabled.value    = value}
      o.on_event(:moved)       {|move|  @board_next_move.value = move}
      o.on_event(:board_reset) {|board| @board.value           = board}
    end

    @reset_enabled.value = true
    @solve_enabled.value = true
    @move_path.reset(Move.nu_initial(initial_board))
    @status_text.value = 'w e l c o m e'
  end

  def bind_button(observable, &on_event_block)
    observable.on_event(&on_event_block)
    return {
      :enabled => AttributeVMBinding.new(observable, :enabled)
    }
  end

  def bind_label(observable)
    return {
      :text => AttributeVMBinding.new(observable, :text)
    }
  end

  def bind_board(observable, &on_move_block)
    observable.on_event(&on_move_block)
    %i[board next_move].collect do |attr|
      [attr, AttributeVMBinding.new(observable, attr)]
    end.to_h
  end

  def on_new_game(game)
    Game.instance.reset(game)
    on_reset
  end

  def on_reset
    @move_path.reset(Move.nu_initial(Game.instance.initial_board))
    @status_text.value = 'r e s e t'
    @solve_details_text.value = ' '
  end

  def on_move(c, direction)
    @current_board.value.move_piece(c, direction).tap do |new_board|
      @move_path << Move.new(new_board, c, direction) if new_board
    end
  end

  def on_solve
    @solve_enabled.value = false
    @reset_enabled.value = false
    @undo_enabled.value  = false
    @next_enabled.value  = false
    @status_text.value   = 's o l v i n g'

    @solve_start = Time.now
    Solver.instance.begin_solving(@current_board.value)

    @task_agent.add_repetitive_task {solve_task}
  end

  def solve_task
    Solver.instance.resume if Solver.instance.solving?
    return Solver.instance.solving?
  end

  def on_solver_trying_candidates(paths, depth)
    @status_text.value = "solving (#{(Time.now - @solve_start).truncate}s elapsed)"
    @solve_details_text.value = "candidates: #{paths.count} at depth #{depth}"
  end

  def on_solver_wins_found(paths, depth)
    puts "wins: #{paths.count} at depth #{depth}"
  end

  def on_solutions_found(solutions)
    @status_text.value = "solved in #{(Time.now - @solve_start).truncate}s"
    @solve_details_text.value = "found #{solutions.count} solutions"
    @move_path.add_solution(solutions.first)
    @solve_enabled.value = true
    @reset_enabled.value = true
  end
end
